Popular Searches
Popular Course Categories
Popular Courses

Scheduling One-Time and Periodic Work

Scheduling One-Time and Periodic Work

Background Tasks and Notifications

Scheduling One-Time and Periodic Work in Android

Course: Android App Development

Topic: Scheduling One-Time and Periodic Work

Course Link: Android App Development Course

Demo Link: Register for a Free Course Demo

Official Website: JustAcademy


1. Introduction to Work Scheduling in Android

Android applications frequently need to perform tasks that do not have to run immediately while the user is interacting with the screen. Examples include uploading files, synchronizing data, downloading content, sending logs to a server, and refreshing cached information.

Work scheduling allows an application to arrange background operations so that they can execute when the required conditions are satisfied. Android provides several APIs for background execution, and WorkManager is the recommended solution for persistent background work that should continue to be scheduled even if the application process exits or the device restarts.

WorkManager supports two common scheduling patterns:

  • One-time work: Executes a task once when its constraints and scheduling requirements are satisfied.
  • Periodic work: Repeats a task at an approximate interval, subject to Android scheduling policies and system restrictions.

2. Learning Objectives

  • Understand background work and the role of WorkManager.
  • Differentiate one-time work from periodic work.
  • Create and configure Worker classes.
  • Schedule tasks using OneTimeWorkRequest and PeriodicWorkRequest.
  • Apply constraints, delays, retries, and backoff policies.
  • Pass input data to workers and retrieve their output.
  • Observe work status and handle cancellation.
  • Implement practical background processing in Android applications.

3. What Is WorkManager?

WorkManager is part of Android Jetpack and provides an API for scheduling deferrable, reliable background work. It manages execution according to operating-system restrictions, application requirements, and device conditions.

Key Features

  • Persistent scheduling: Work requests can survive application process termination and device restarts.
  • Constraint support: Work can require conditions such as network connectivity or sufficient battery status.
  • Retry handling: Failed work can be retried using configurable backoff policies.
  • Work chaining: Multiple tasks can be connected through dependency relationships.
  • Status observation: Applications can observe whether work is enqueued, running, succeeded, failed, or cancelled.
  • Unique work: Named work helps prevent unwanted duplicate scheduling.

WorkManager is intended for work that can be deferred. It is not designed for exact-time execution, continuous real-time processing, or tasks that must run at a precise second.

4. Setting Up WorkManager

For an Android project using Kotlin and Gradle, add the WorkManager dependency to the app-level Gradle file. Use a stable version compatible with your project's Android Gradle Plugin and Kotlin configuration.

dependencies {
    implementation("androidx.work:work-runtime-ktx:2.10.5")
}

The version shown is an example dependency declaration. Check the official Android documentation for the latest stable release and compatibility guidance before using it in a production project.

After adding the dependency, synchronize the Gradle project. The Kotlin extensions allow you to define workers and requests using convenient APIs.

5. Understanding Worker Classes

A worker contains the code that performs a background operation. For a straightforward task, extend the Worker class and implement its doWork() method.

The method returns a result indicating whether the task succeeded, failed, or should be retried.

  • Result.success() indicates successful completion.
  • Result.failure() indicates permanent failure.
  • Result.retry() requests another attempt according to the configured backoff policy.

Example: Creating a Worker

import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters

class DataSyncWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

    override fun doWork(): Result {
        return try {
            Log.d("DataSyncWorker", "Background synchronization started")

            // Perform a short, synchronous background operation.
            // Replace this section with your actual sync logic.

            Log.d("DataSyncWorker", "Background synchronization completed")
            Result.success()
        } catch (exception: Exception) {
            Log.e("DataSyncWorker", "Synchronization failed", exception)
            Result.retry()
        }
    }
}

Important: This example demonstrates the worker structure. Production code should distinguish temporary failures from permanent failures instead of retrying every exception. Do not perform lengthy blocking operations inside a worker without considering execution limits.

6. Understanding One-Time Work

One-time work is used when an operation needs to run once rather than repeat on a schedule. The operation may execute later because of system optimization, constraints, or other scheduled work.

Common Use Cases

  • Uploading a photograph selected by the user.
  • Synchronizing recently updated records.
  • Sending diagnostic logs to a server.
  • Processing a file in the background.
  • Submitting locally stored data when a network connection becomes available.

Creating a One-Time Work Request

Use OneTimeWorkRequestBuilder to create a request and WorkManager.enqueue() to submit it.

import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager

val uploadRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>()
        .build()

WorkManager.getInstance(applicationContext)
    .enqueue(uploadRequest)

When the request is enqueued, WorkManager schedules it for execution. The worker runs when scheduling requirements and system conditions permit.

Adding an Initial Delay

Sometimes a task should not begin immediately. Use setInitialDelay() to specify a minimum delay before the first execution becomes eligible.

import java.util.concurrent.TimeUnit
import androidx.work.OneTimeWorkRequestBuilder

val delayedRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>()
        .setInitialDelay(15, TimeUnit.MINUTES)
        .build()

WorkManager.getInstance(applicationContext)
    .enqueue(delayedRequest)

This schedules work with an initial delay of 15 minutes. It does not guarantee execution at the exact 15-minute mark.

7. Understanding Periodic Work

Periodic work is intended for operations that should be repeated at regular intervals, such as refreshing cached information or synchronizing application data.

Use PeriodicWorkRequestBuilder to define the repetition interval. Android does not guarantee execution at an exact time because WorkManager considers constraints, battery optimization, and system scheduling.

Minimum Periodic Interval

WorkManager's minimum periodic interval is 15 minutes. Shorter intervals are not supported for standard periodic work requests.

Common Use Cases

  • Periodic synchronization of application records.
  • Refreshing cached content.
  • Cleaning up temporary files.
  • Uploading analytics or diagnostic information.
  • Checking for pending background maintenance tasks.

8. Creating a Periodic Work Request

The following example schedules a worker to run periodically with a 15-minute interval.

import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit

val periodicSyncRequest =
    PeriodicWorkRequestBuilder<DataSyncWorker>(
        15, TimeUnit.MINUTES
    ).build()

WorkManager.getInstance(applicationContext)
    .enqueue(periodicSyncRequest)

The request makes the worker eligible to run periodically. Actual execution may occur later than the requested interval, and subsequent runs depend on scheduling conditions.

Using a Longer Interval

For tasks that do not require frequent updates, a longer interval can reduce battery consumption and unnecessary network activity.

val dailyCleanupRequest =
    PeriodicWorkRequestBuilder<DataSyncWorker>(
        24, TimeUnit.HOURS
    ).build()

WorkManager.getInstance(applicationContext)
    .enqueue(dailyCleanupRequest)

This example uses a 24-hour repeat interval. It is an interval-based schedule, not a guarantee that the task will run at the same clock time every day.

9. One-Time Work vs Periodic Work

Feature One-Time Work Periodic Work
Purpose Perform a task once Repeat a task at intervals
Request builder OneTimeWorkRequestBuilder PeriodicWorkRequestBuilder
Repeat behavior Does not repeat automatically Repeats according to its interval
Minimum interval Not applicable 15 minutes
Initial delay Supported Not configured using the one-time initial-delay API
Constraints Supported Supported
Retries Supported Supported
Typical example Upload a selected file Refresh cached data periodically

10. Applying Constraints to Background Work

Constraints define the conditions that must be satisfied before WorkManager can execute a task. They help prevent operations from running under unsuitable conditions.

Common constraints include:

  • NetworkType.CONNECTED — requires a network connection.
  • NetworkType.UNMETERED — requires an unmetered network, such as suitable Wi-Fi connectivity.
  • setRequiresCharging(true) — requires the device to be charging.
  • setRequiresBatteryNotLow(true) — requires the battery not to be low.
  • setRequiresStorageNotLow(true) — requires sufficient storage availability according to the system's low-storage status.

Example: Network Constraint

import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder

val syncConstraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()

val constrainedSyncRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>()
        .setConstraints(syncConstraints)
        .build()

WorkManager.getInstance(applicationContext)
    .enqueue(constrainedSyncRequest)

WorkManager waits until the required network condition is satisfied before running the task. If the condition becomes unmet while work is running, WorkManager may stop the worker and retry it when conditions allow.

11. Combining Periodic Work with Constraints

Periodic work can also use constraints. For example, a data synchronization task can be configured to run periodically only when a network connection is available.

import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import java.util.concurrent.TimeUnit

val periodicConstraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresBatteryNotLow(true)
    .build()

val periodicRequest =
    PeriodicWorkRequestBuilder<DataSyncWorker>(
        6, TimeUnit.HOURS
    )
        .setConstraints(periodicConstraints)
        .build()

WorkManager.getInstance(applicationContext)
    .enqueue(periodicRequest)

The worker is eligible to run at its periodic opportunities when the constraints are satisfied. The six-hour interval is not an exact execution deadline.

12. Passing Input Data to a Worker

Sometimes a worker needs information such as a record identifier, upload category, or file name. WorkManager supports lightweight key-value input through the Data class.

import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder

val inputData = Data.Builder()
    .putString("task_type", "upload")
    .putInt("record_id", 101)
    .build()

val request =
    OneTimeWorkRequestBuilder<DataSyncWorker>()
        .setInputData(inputData)
        .build()

WorkManager.getInstance(applicationContext)
    .enqueue(request)

Read the supplied values inside the worker using inputData.

override fun doWork(): Result {
    val taskType = inputData.getString("task_type")
    val recordId = inputData.getInt("record_id", -1)

    if (recordId < 0) {
        return Result.failure()
    }

    // Perform the operation using taskType and recordId.
    return Result.success()
}

WorkManager's Data is intended for small values, not large files, images, or complex objects. Pass a file URI or database identifier instead of embedding large content.

13. Returning Output Data

A worker can return small results that downstream workers or observers may use.

override fun doWork(): Result {
    val processedCount = 25

    val output = Data.Builder()
        .putInt("processed_count", processedCount)
        .build()

    return Result.success(output)
}

Output data can be accessed from the corresponding work information after execution. For workflows with multiple tasks, output values can also be used when configuring dependent work.

14. Handling Failures and Retry Policies

Background operations can fail because of network interruptions, server errors, unavailable resources, or temporary device conditions. WorkManager supports retry policies for recoverable failures.

Returning a Retry Result

override fun doWork(): Result {
    val operationSucceeded = performSync()

    return if (operationSucceeded) {
        Result.success()
    } else {
        Result.retry()
    }
}

The performSync() method in this example represents an application-defined operation. Implement it with appropriate error handling and a clear distinction between temporary and permanent failures.

Configuring Backoff

A backoff policy determines how WorkManager delays retry attempts. The two main policies are:

  • EXPONENTIAL: The delay increases between successive retry attempts.
  • LINEAR: The delay increases linearly between retry attempts.
import androidx.work.BackoffPolicy
import androidx.work.OneTimeWorkRequestBuilder
import java.util.concurrent.TimeUnit

val retryRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>()
        .setBackoffCriteria(
            BackoffPolicy.EXPONENTIAL,
            30,
            TimeUnit.SECONDS
        )
        .build()

The initial backoff delay is configurable, subject to WorkManager's minimum backoff requirements and system behavior. Retries are not unlimited guarantees of eventual success; use sensible limits and failure handling in your application logic.

15. Observing Work Status

WorkManager exposes work information so the application can observe the state of a scheduled task. This can be useful for showing upload progress, indicating completion, or displaying an error message.

Typical states include:

  • ENQUEUED: The work is waiting to run.
  • RUNNING: The worker is executing.
  • SUCCEEDED: The work completed successfully.
  • FAILED: The work failed permanently.
  • BLOCKED: The work is waiting for prerequisite work.
  • CANCELLED: The work was cancelled.

Example: Observing a One-Time Request

val workManager = WorkManager.getInstance(applicationContext)

workManager.getWorkInfoByIdLiveData(uploadRequest.id)
    .observe(lifecycleOwner) { workInfo ->
        workInfo ?: return@observe

        when (workInfo.state) {
            androidx.work.WorkInfo.State.ENQUEUED -> {
                // Show that the task is queued.
            }
            androidx.work.WorkInfo.State.RUNNING -> {
                // Show that processing is in progress.
            }
            androidx.work.WorkInfo.State.SUCCEEDED -> {
                // Update the UI after successful completion.
            }
            androidx.work.WorkInfo.State.FAILED -> {
                // Display an appropriate failure message.
            }
            androidx.work.WorkInfo.State.CANCELLED -> {
                // Handle cancellation.
            }
            else -> {
                // Handle any other applicable state.
            }
        }
    }

In modern applications, expose work information through a ViewModel and collect observable state using the UI framework in use. Avoid placing lifecycle-sensitive observation directly in long-lived background components.

16. Managing Unique Work

Repeatedly enqueuing the same request can result in duplicate background operations. WorkManager supports unique work names to help control this behavior.

Unique One-Time Work

import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager

val request =
    OneTimeWorkRequestBuilder<DataSyncWorker>()
        .build()

WorkManager.getInstance(applicationContext)
    .enqueueUniqueWork(
        "data_sync_once",
        ExistingWorkPolicy.KEEP,
        request
    )

With KEEP, an existing unfinished work sequence with the same unique name is retained rather than replaced by the new request.

Unique Periodic Work

import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import java.util.concurrent.TimeUnit

val periodicRequest =
    PeriodicWorkRequestBuilder<DataSyncWorker>(
        12, TimeUnit.HOURS
    ).build()

WorkManager.getInstance(applicationContext)
    .enqueueUniquePeriodicWork(
        "regular_data_sync",
        ExistingPeriodicWorkPolicy.KEEP,
        periodicRequest
    )

Unique periodic work prevents multiple periodic schedules with the same unique name from being created independently. Select the appropriate policy when an existing schedule must be retained or updated. Available policy options depend on the WorkManager version.

17. Cancelling Scheduled Work

Applications may need to cancel work when a user signs out, deletes an account, or disables a feature.

Cancel Work by ID

WorkManager.getInstance(applicationContext)
    .cancelWorkById(uploadRequest.id)

Cancel Unique Work by Name

WorkManager.getInstance(applicationContext)
    .cancelUniqueWork("regular_data_sync")

Cancellation requests stop eligible work and prevent future execution of the cancelled work sequence. A worker that is already running may receive a stop signal, so worker code should respond appropriately and clean up resources.

18. Understanding Work Chaining

Work chaining allows multiple operations to execute in a specified order. For example, an application may need to download data, process it, and then upload a result.

import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager

val downloadRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>().build()

val processRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>().build()

val uploadRequest =
    OneTimeWorkRequestBuilder<DataSyncWorker>().build()

WorkManager.getInstance(applicationContext)
    .beginWith(downloadRequest)
    .then(processRequest)
    .then(uploadRequest)
    .enqueue()

This illustrates the chaining API. In a real application, use separate workers for downloading, processing, and uploading, and pass required output data between them. Each stage should handle its own errors and dependencies.

19. Best Practices for One-Time and Periodic Work

  • Use WorkManager for deferrable background operations that should be reliably scheduled.
  • Choose one-time work for a single operation and periodic work for recurring maintenance.
  • Remember that periodic work has a minimum interval of 15 minutes.
  • Do not rely on WorkManager for exact-time execution.
  • Set constraints when tasks require a network connection, charging, or other device conditions.
  • Keep workers focused on a specific responsibility.
  • Use unique work names to avoid duplicate scheduling.
  • Implement retries only for failures that may recover.
  • Make network operations idempotent where possible so retries do not create duplicate server-side effects.
  • Use input and output data for small values only.
  • Do not place passwords, authentication tokens, or other secrets in logs or unnecessary work data.
  • Respect cancellation and clean up resources when work is stopped.
  • Test behavior under network loss, application process termination, and device restart.
  • Use a foreground service or another suitable API when the task requires ongoing, user-visible execution rather than deferrable scheduled work.

20. Common Mistakes and Troubleshooting

Problem Possible Cause Solution
Worker does not start immediately WorkManager schedules tasks according to system conditions. Check constraints and understand that execution is not immediate or exact-time.
Periodic work runs later than expected Battery optimization, constraints, or system scheduling. Design for approximate intervals rather than exact timestamps.
Periodic request uses an interval below 15 minutes Minimum periodic interval restriction. Use at least 15 minutes or select a different API if the use case requires another execution model.
Task repeatedly fails Permanent error or incorrect retry logic. Differentiate temporary errors from permanent failures and inspect logs.
Duplicate synchronization occurs Multiple requests were enqueued. Use unique work and appropriate conflict policies.
Worker stops unexpectedly Constraints become unmet or execution limits are reached. Make work resumable and ensure resources are cleaned up.
Large data cannot be passed as input WorkManager Data is designed for small values. Store content in a file or database and pass a reference.

21. Practical Project: Automatic Data Synchronization

Project objective: Build an Android feature that synchronizes application data in the background and supports both manual one-time synchronization and scheduled periodic synchronization.

Functional Requirements

  • A Sync Now button triggers one-time synchronization.
  • Periodic work checks for pending records at a configured interval.
  • Synchronization runs only when a network connection is available.
  • The UI displays the current work status.
  • Temporary failures can be retried.
  • The application avoids scheduling duplicate periodic work.
  • Users can disable periodic synchronization.

Suggested Implementation Plan

  1. Create a worker responsible for synchronizing pending records.
  2. Implement repository methods for retrieving and updating local data.
  3. Configure a network constraint.
  4. Create a one-time request for manual synchronization.
  5. Create a periodic request for recurring synchronization.
  6. Enqueue periodic work using a unique name.
  7. Observe work status through a ViewModel.
  8. Add a cancellation or disable-synchronization action.
  9. Test the feature with a connected network, disconnected network, and simulated server failures.

22. Exercises for Practice

  1. Create a one-time worker that writes a log message after a successful operation.
  2. Schedule a one-time task with an initial delay of five minutes.
  3. Create a periodic worker that runs at the minimum supported interval.
  4. Configure a network constraint for a data upload task.
  5. Pass a record ID to a worker using the Data class.
  6. Return a processed-record count through output data.
  7. Configure an exponential backoff policy for retryable failures.
  8. Observe the state of a work request and update a UI status label.
  9. Enqueue unique periodic work and test the KEEP policy.
  10. Cancel a scheduled task using its work ID.

23. Interview Questions and Answers

Q1. What is WorkManager in Android?

WorkManager is a Jetpack library for scheduling deferrable background work that needs reliable scheduling across application process termination and device restarts.

Q2. What is one-time work?

One-time work is a request to execute a background task once when its scheduling requirements are satisfied.

Q3. What is periodic work?

Periodic work repeats a background task at an approximate interval. Its execution time is affected by Android's scheduling and optimization policies.

Q4. What is the minimum periodic interval?

The minimum periodic interval supported by WorkManager is 15 minutes.

Q5. Does WorkManager guarantee execution at an exact time?

No. WorkManager schedules deferrable work and does not guarantee exact-time execution.

Q6. What is a constraint?

A constraint is a condition that must be satisfied for a worker to run, such as network availability or charging status.

Q7. What is the difference between Result.failure() and Result.retry()?

Result.failure() marks work as permanently failed, while Result.retry() requests another attempt according to the configured backoff policy.

Q8. Why use unique work?

Unique work helps prevent duplicate work sequences by associating a unique name and a conflict policy with scheduled work.

Q9. Can periodic work have constraints?

Yes. Periodic work supports constraints such as network connectivity and battery conditions.

Q10. When should WorkManager not be used?

It should not be used when a task requires execution at an exact time or continuous real-time activity. Choose an API designed for the specific execution requirement.

24. Quick Revision Checklist

  • Understand the purpose of WorkManager.
  • Know when to choose one-time versus periodic work.
  • Use the appropriate work request builder.
  • Remember the 15-minute minimum periodic interval.
  • Configure constraints based on task requirements.
  • Use input and output Data for small values.
  • Handle success, failure, retries, and cancellation correctly.
  • Observe work status and update the UI safely.
  • Use unique work to manage duplicate scheduling.
  • Test background work under realistic device conditions.

25. Conclusion

Scheduling one-time and periodic work is an important part of reliable Android application development. WorkManager provides a structured way to run deferrable tasks, configure execution constraints, retry temporary failures, observe status, and maintain scheduled work across process termination and device restarts.

One-time work is appropriate for individual operations such as uploading a file or synchronizing a record. Periodic work is suitable for recurring tasks such as refreshing cached information or performing background maintenance. Understanding their differences helps developers build efficient applications that respect battery life, system restrictions, and user expectations.

To develop these skills further, explore the Android App Development course at JustAcademy and register for a course demo.

Previous Next
whatsapp